---------------------PARAMETERS FOR A/B TEST--------------------------------
local json    = require('json')
local utility = require('lua-dir')

local gray_backupped
local gray_restoring
local server_sids = {}
local success_created_files = {}
local files_to_be_create= {}
local jservers_map = {}
local gray_sinc_fns = {}
local gray_hinc_fns = {}
local ngx_bin_name = ngx.var.nginx_binary_name
local ngx_pid_name = ngx.var.nginx_pid_name
local ngx_conf_name = ngx.var.nginx_conf_name
local ngx_upstream_a_loc = ngx.var.nginx_upstream_a_loc
local ngx_upstream_b_loc = ngx.var.nginx_upstream_b_loc
local ngx_backend_loc = ngx.var.nginx_backend_status_loc

--local ngx_gray_deploy_type = ngx.var.nginx_gray_deploy_type

local gray_query_loc = ngx.var.gray_query_loc
local gray_sleep_loc = ngx.var.gray_sleep_loc
local gray_try_times = ngx.var.gray_try_times
local gray_backend_loc = ngx.var.gray_backend_loc
local gray_upstream_a_loc = ngx.var.gray_upstream_a
local gray_upstream_b_loc = ngx.var.gray_upstream_b

--local ngx_gray_query_port = ngx.var.nginx_gray_query_port
local ngx_conf_prefix = ngx.var.nginx_conf_prefix
local gray_json_dir = "json/"
local gray_conf_name = "gray_conf/"
local gray_backup_dir = "backup/"
local gray_test_dir = "test/"
local gray_res_dir = "resource/"
local gray_error_dir = "error_confs/"

local dowhat
dowhat = ngx.var.arg_dowhat

local nginx_server_name
local test_backend
if not string.match(ngx_conf_prefix, "/$") then
    ngx_conf_prefix = ngx_conf_prefix.."/"
end

gray_try_times = tonumber(gray_try_times)
if not gray_try_times then
    gray_try_times = 180
end

local gray_test_fdir = ngx_conf_prefix..gray_conf_name..gray_test_dir
local gray_json_fdir = ngx_conf_prefix..gray_conf_name..gray_json_dir
local gray_backup_fdir = ngx_conf_prefix..gray_conf_name..gray_backup_dir
local gray_error_fdir = ngx_conf_prefix..gray_conf_name..gray_error_dir
local gray_lock_file = ngx_conf_prefix..gray_conf_name.."gray.lock"

if not string.match(ngx_conf_name, "^/") then
    ngx_conf_name = ngx_conf_prefix..ngx_conf_name
end

if gray_backend_loc and gray_upstream_a_loc and gray_upstream_b_loc then test_backend = 1 end

test_backend = nil

if not gray_query_loc then
    gray_query_loc = "/admin/query"
end

--if not ngx_gray_query_port then
--    ngx_gray_query_port = "80"
--end


-------------------------UTILITY FUNCTIONS ------------------
local OK = 0
local ERROR = -1


local function sleep()
    ngx.location.capture(gray_sleep_loc) 
end


function traceback()
    local level = 1
    while true do
        local info = debug.getinfo(level, "Sl")
        if not info then break end
        if info.what == "C" then    -- is a C function?
            print(level, "C function")
        else   -- a Lua function
            print(string.format("[%s]:%d",
            info.short_src, info.currentline))
        end
        level = level + 1
    end
end


local function copy(src, dst)
    local sfd, dfd, m
    sfd, m = io.open(src, "r")
    if sfd == nil then return nil, m end

    dfd, m = io.open(dst, "w")
    if dfd == nil then
        sfd:close()
        return nil, m
    end

    dfd:write(sfd:read("*all"))
    dfd:close()
    sfd:close()
    return 1
end


local function log_print(result, ...)
    local out_json;
    if result == 'SUCCESS' then
        out_json = "{\"status\":\"SUCCESS\"}"
    elseif result == 'NO' then
        out_json = "{\"status\":\"NO\"}"
    elseif result == 'RAW' then
        out_json = ...
    else
        local err_str, err_code, extr_err_str = ...
        if err_str then
            if extr_err_str then err_str = err_str..extr_err_str end
            err_str = string.gsub(err_str, "\"", "")
            out_json = string.format("{\"status\":\"ERROR\",\"ecode\":\"%d\",\"emsg\":\"%s\"}", err_code, err_str) 
        else
            out_json = string.format("{\"status\":\"ERROR\",\"ecode\":\"%d\"}", err_code) 
        end
    end
    ngx.log(ngx.WARN, out_json)
    return ngx.print(out_json)
end


local function icheck(value, vtype)
    local t = type(value)
    local vt = vtype
    if vtype == 'array' then vt = 'table' end

    if t ~= vt then return nil end

    if vt == 'string' then
        if value == "" then return nil  end
        if string.match(value, ";") then
            return nil
        else
            return value
        end
    elseif vt == 'table' then
        for k, v in pairs(value) do
            if vtype == 'array' then
                if not icheck(k, 'number') then return nil end
            else
                if not icheck(k, 'string') then return nil end
            end
            if not icheck(v, 'string') then return nil end
        end
        return value
    elseif vt == 'number' then
        return value
    else
        return nil
    end
end


local function  is_file_exist(file_name)
    local f, m = io.open(file_name, "r")
    if f == nil then
        return nil, m
    end
    f:close()
    return 1
end


-----------------------NGX CONF PARSER------------------------
local LF = 10           -- '\n' huanhang
local CR = 13           -- huiche
local TAB = 9           -- '\t'
local SEMI = 59         -- semicolon ;
local SPACE = 32        -- ' '
local SHARP = 35        -- #
local DOLLAR = 36       -- '$'
local QUOTE = 92        -- \\
local SQUOTE = 39       -- "'"
local DQUOTE = 34       -- '"'
local LBRACE = 123      -- {
local RBRACE = 125      -- }
local RBRACKET = 41     -- )

-- TODO skip ' '  " "
-- buffer size
local CONF_BUFFER  =    4096

local char_map = {}
char_map[LF] = '\\n'
char_map[CR] = '\\r'
char_map[TAB] = '\\t'
char_map[SEMI] = ';'
char_map[SPACE] = 'space'
char_map[DOLLAR] = '$'
char_map[SHARP] = '#'
char_map[QUOTE] = '\\'
char_map[SQUOTE] = 'single quotes'
char_map[DQUOTE] = 'double quotes'
char_map[LBRACE] = '{'
char_map[RBRACE] = '}'
char_map[RBRACKET] = ')'

local CONF_NOARGS  =    0x00000001
local CONF_TAKE1   =    0x00000002
local CONF_TAKE2   =    0x00000004
local CONF_TAKE3   =    0x00000008
local CONF_TAKE4   =    0x00000010
local CONF_TAKE5   =    0x00000020
local CONF_TAKE6   =    0x00000040
local CONF_TAKE7   =    0x00000080

local CONF_MAX_ARGS  =  8

local CONF_TAKE12   =   12
local CONF_TAKE13   =   13
local CONF_TAKE23   =   23
local CONF_TAKE123  =   123
local CONF_TAKE1234 =   1234
local CONF_FLAG     =   0x00000200
local CONF_ANY      =   0x00000400
local CONF_1MORE    =   0x00000800
local CONF_2MORE    =   0x00001000


local argument_number = {
    {CONF_NOARGS},
    {CONF_TAKE1, CONF_TAKE12, CONF_TAKE123, CONF_TAKE1234},
    {CONF_TAKE2, CONF_TAKE12, CONF_TAKE123, CONF_TAKE1234, CONF_TAKE23},
    {CONF_TAKE3, CONF_TAKE123, CONF_TAKE1234, CONF_TAKE23},
    {CONF_TAKE4, CONF_TAKE1234},
    {CONF_TAKE5},
    {CONF_TAKE6},
    {CONF_TAKE7}
}


local CONF_BLOCK_START = 1
local CONF_BLOCK_DONE = 2
local CONF_FILE_DONE = 3

local CORE_MODULE = 1
local HTTP_MODULE = 2
--local CONF_MODULE = 3

local MAIN_CONF = 1

local HTTP_MAIN_CONF = 1
local HTTP_SRV_CONF = 2
local HTTP_LOC_CONF = 3
local HTTP_UPS_CONF = 4
local HTTP_SIF_CONF = 5
local HTTP_LIF_CONF = 6
local HTTP_GEO_CONF = 7

local ANY_CONF = "1111"

local core_main_conf = {}
local http_main_conf = {}
local http_srv_conf = {}
local http_loc_conf = {}
local http_ups_conf = {}
local http_srv_loc_conf = {}
local http_loc_lif_conf = {}
local http_srv_sif_loc_lif_conf = {}

local include_type = {}

core_main_conf[MAIN_CONF] = 1
http_main_conf[HTTP_MAIN_CONF] = 1
http_srv_conf[HTTP_SRV_CONF] = 1
http_loc_conf[HTTP_LOC_CONF] = 1
http_ups_conf[HTTP_UPS_CONF] = 1
http_srv_loc_conf[HTTP_SRV_CONF] = 1
http_srv_loc_conf[HTTP_LOC_CONF] = 1
http_loc_lif_conf[HTTP_LIF_CONF] = 1
http_loc_lif_conf[HTTP_LOC_CONF] = 1
http_srv_sif_loc_lif_conf[HTTP_SRV_CONF] = 1
http_srv_sif_loc_lif_conf[HTTP_LOC_CONF] = 1
http_srv_sif_loc_lif_conf[HTTP_LIF_CONF] = 1
http_srv_sif_loc_lif_conf[HTTP_SIF_CONF] = 1

include_type[MAIN_CONF] = 1
include_type[HTTP_MAIN_CONF] = 1
include_type[HTTP_SRV_CONF] = 1

-- not support
include_type[HTTP_LOC_CONF] = 1
include_type[HTTP_LIF_CONF] = 1
include_type[HTTP_SIF_CONF] = 1

local invalid_include_type = {}
invalid_include_type[HTTP_LOC_CONF] = "location"
invalid_include_type[HTTP_LIF_CONF] = "location if"
invalid_include_type[HTTP_SIF_CONF] = "server if"

local CONF_UNKNOWN = "1000"
local CONF_INCLUDE = "1001"

local parse_file = 0
local parse_block = 1

local global_cf = {}
--local ngx_conf = {}
global_cf.module_type = CORE_MODULE
global_cf.cmd_type = MAIN_CONF
global_cf.conf_file = {}
global_cf.conf_file.buffer = {}
global_cf.ngx_conf = {}

-- to save all upstream/server/geo
local ngx_conf_upstreams = {}
local ngx_conf_servers = {}
local ngx_conf_geos = {}
local ngx_conf_http

local ngx_conf_checksum

local ngx_conf_server_name_map = {}
local ngx_conf_ups_name_map = {}

-- private functions
local http_block
local conf_include
local http_geo_block
local http_server_block
local http_location_block
local http_upstream_server
local http_upstream_block
local http_proxy_pass
local http_rewrite_if
local http_rewrite_set
local http_server_listen
local http_server_name

local ngx_conf_parse


local function include_file_not_ignored(filename)
    if not filename then return true end
    local ignored_files = {}
    ignored_files["mime.types"] = true
    ignored_files["tmd_main.conf"] = true
    ignored_files["tmd_http.conf"] = true
    ignored_files["tmd_loc.conf"] = true
    ignored_files["tmd_ip.conf"] = true
    local basename = string.match(filename, "([^/]+)$") 
    if not basename then return true end
    return not ignored_files[basename]
end


http_block = function(cf, cmd)
    local module_type = cf.module_type
    local cmd_type = cf.cmd_type
    local ngx_conf = cf.ngx_conf

    local directive = {}
    local http = {}

    if ngx_conf.INCLUDE then
        return nil, "not support http block included in a file"
    end

    ngx_conf_http = http

    directive.ARGS = cf.args
    directive.BLOCK = http

    cf.ngx_conf[#cf.ngx_conf + 1] = directive

    -- save parent block (core or include)
    http.PARENT = cf.ngx_conf
    http.INDEX = #cf.ngx_conf
    directive.CTYPE = HTTP_MAIN_CONF

    cf.module_type = HTTP_MODULE
    cf.cmd_type = HTTP_MAIN_CONF

    ngx_conf.http = http
    cf.ngx_conf = ngx_conf.http
    local rc, m = ngx_conf_parse(cf)
    if rc == nil then
        return nil, m
    end
    cf.module_type = module_type
    cf.cmd_type = cmd_type
    cf.ngx_conf = ngx_conf

    return OK
end


http_rewrite_if = function(cf, cmd)
    local cmd_type = cf.cmd_type
    local ngx_conf = cf.ngx_conf
    local if_block = {}

    local directive = {}
    directive.ARGS = cf.args
    directive.BLOCK = if_block

    cf.ngx_conf[#cf.ngx_conf + 1] = directive

    if_block.INDEX = #cf.ngx_conf
    if_block.PARENT = cf.ngx_conf
    if_block.ARGS = cf.args

    if ngx_conf.INCLUDE and not ngx_conf.PARENT.SERVER then
        return nil, "not support if block in a location included in a file alone"
    end

    if ngx_conf.ifs == nil then
        ngx_conf.ifs = {}
    end

    ngx_conf.ifs[#ngx_conf.ifs + 1] = if_block

    if cmd_type == HTTP_SRV_CONF then
        directive.CTYPE = HTTP_SIF_CONF
        cf.cmd_type = HTTP_SIF_CONF
    else
        directive.CTYPE = HTTP_LIF_CONF
        cf.cmd_type = HTTP_LIF_CONF
    end
    cf.ngx_conf = if_block
    local rc, m = ngx_conf_parse(cf)
    if rc == nil then
        return nil, m
    end
    cf.cmd_type = cmd_type
    cf.ngx_conf = ngx_conf

    return OK
end


http_geo_block = function(cf, cmd)
    local cmd_type = cf.cmd_type
    local ngx_conf = cf.ngx_conf
    local geo = {}

    local directive = {}
    directive.ARGS = cf.args
    directive.BLOCK = geo

    cf.ngx_conf[#cf.ngx_conf + 1] = directive

    geo.INDEX = #cf.ngx_conf
    geo.PARENT = cf.ngx_conf
    geo.ARGS = cf.args
    directive.CTYPE= HTTP_GEO_CONF

    -- parse
    local parse_conf = {}
    parse_conf.var_name = cf.args[#cf.args]
    geo.PARSE_CONF = parse_conf

    if ngx_conf.geos == nil then
        ngx_conf.geos = {}
    end

    ngx_conf.geos[#ngx_conf.geos + 1] = geo

    ngx_conf_geos[#ngx_conf_geos + 1] = geo

    cf.cmd_type = HTTP_GEO_CONF
    cf.ngx_conf = geo
    local rc, m = ngx_conf_parse(cf)
    if rc == nil then
        return nil, m
    end
    cf.cmd_type = cmd_type
    cf.ngx_conf = ngx_conf

    return OK
end


http_upstream_block = function(cf, cmd)
    local cmd_type = cf.cmd_type
    local ngx_conf = cf.ngx_conf
    local upstream = {}

    local directive = {}
    directive.ARGS = cf.args
    directive.BLOCK = upstream

    cf.ngx_conf[#cf.ngx_conf + 1] = directive

    upstream.INDEX = #cf.ngx_conf
    upstream.PARENT = cf.ngx_conf
    upstream.ARGS = cf.args

    directive.CTYPE= HTTP_UPS_CONF

    -- parse
    local parse_conf = {}
    parse_conf.name = cf.args[#cf.args]
    upstream.PARSE_CONF = parse_conf

    ngx_conf_ups_name_map[parse_conf.name] = upstream

    if ngx_conf.upstreams == nil then
        ngx_conf.upstreams = {}
    end

    ngx_conf.upstreams[#ngx_conf.upstreams + 1] = upstream

    ngx_conf_upstreams[#ngx_conf_upstreams + 1] = upstream

    cf.cmd_type = HTTP_UPS_CONF
    cf.ngx_conf = upstream
    local rc, m = ngx_conf_parse(cf)
    if rc == nil then
        return nil, m
    end
    cf.cmd_type = cmd_type
    cf.ngx_conf = ngx_conf

    return OK
end


http_server_block = function(cf, cmd)
    local cmd_type = cf.cmd_type
    local ngx_conf = cf.ngx_conf
    local server = {}

    local directive = {}
    directive.ARGS = cf.args
    directive.BLOCK = server

    cf.ngx_conf[#cf.ngx_conf + 1] = directive

    -- save the position of this server in http block
    server.INDEX = #cf.ngx_conf

    -- save parent block (http or include)
    server.PARENT = cf.ngx_conf

    server.SERVER = 1

    directive.CTYPE= HTTP_SRV_CONF

    -- record exact locations
    local parse_conf = {}
    parse_conf.exact_loc_map = {}
    server.PARSE_CONF = parse_conf

    -- new servers table
    if ngx_conf.servers == nil then
        ngx_conf.servers = {}
    end

    ngx_conf.servers[#ngx_conf.servers + 1] = server

    -- save all servers in global ngx_conf_servers
    ngx_conf_servers[#ngx_conf_servers + 1] = server

    cf.cmd_type = HTTP_SRV_CONF
    cf.ngx_conf = server
    local rc, m = ngx_conf_parse(cf)
    if rc == nil then
        return nil, m
    end
    cf.cmd_type = cmd_type
    cf.ngx_conf = ngx_conf

    return OK
end


http_location_block = function(cf, cmd)
    local cmd_type = cf.cmd_type
    local ngx_conf = cf.ngx_conf
    local location = {}

    location.CF = cf
    location.ARGS = cf.args

    local directive = {}
    directive.ARGS = cf.args
    directive.BLOCK = location

    cf.ngx_conf[#cf.ngx_conf + 1] = directive

    -- save the position of this location in server block
    location.INDEX = #cf.ngx_conf

    location.PARENT = cf.ngx_conf

    directive.CTYPE= HTTP_LOC_CONF

    -- new locations table
    if ngx_conf.locations == nil then
        ngx_conf.locations = {}
    end
    -- parse, for now only exact location is parsed
    local parse_conf = {}
    if cf.cmd_type == HTTP_SRV_CONF then
        if string.sub(cf.args[2], 1, 1) == '=' then
            parse_conf.exact = 1
            if #cf.args == 3 then
                ngx_conf.PARSE_CONF.exact_loc_map[cf.args[3]] = location
                parse_conf.name = cf.args[3]
            else
                ngx_conf.PARSE_CONF.exact_loc_map[string.sub(cf.args[2], 2, -1)] = location
                parse_conf.name = string.sub(cf.args[2], 2, -1)
            end
        end
    end
    location.PARSE_CONF = parse_conf

    ngx_conf.locations[#ngx_conf.locations + 1] = location

    cf.cmd_type = HTTP_LOC_CONF
    cf.ngx_conf = location
    local rc, m = ngx_conf_parse(cf)
    if rc == nil then
        return nil, m
    end
    cf.cmd_type = cmd_type
    cf.ngx_conf = ngx_conf

    return OK
end


conf_include = function(cf, cmd)
    local full_name = cf.args[2]
    local new_cf = {}
    new_cf.ngx_conf = {}
    new_cf.module_type = cf.module_type
    new_cf.cmd_type = cf.cmd_type

    -- TODO include  mime.types; 
    local ngx_conf = cf.ngx_conf

    if ngx_conf.INCLUDE then
        return nil, "not support nested include file"
    end

    if invalid_include_type[cf.cmd_type] then
        return nil, "not support include file in "..invalid_include_type[cf.cmd_type]
    end

    -- new includes table
    if ngx_conf.includes == nil then
        ngx_conf.includes = {}
    end

    local directive = {}
    directive.ARGS = cf.args
    directive.CTYPE= CONF_INCLUDE
    directive.NGX_CONF = new_cf.ngx_conf

    cf.ngx_conf[#cf.ngx_conf + 1] = directive

    new_cf.ngx_conf.INDEX = #cf.ngx_conf
    new_cf.ngx_conf.PARENT = cf.ngx_conf
    new_cf.ngx_conf.INCLUDE = 1

    local parse_conf = {}
    if cf.cmd_type == HTTP_SRV_CONF then
        parse_conf.exact_loc_map = {}
    end
    parse_conf.file_name = cf.args[2]
    new_cf.ngx_conf.PARSE_CONF = parse_conf

    ngx_conf.includes[#ngx_conf.includes + 1] = new_cf.ngx_conf

    if string.sub(full_name, 1, 1) ~= '/' then
        full_name = ngx_conf_prefix..full_name
    end

    local rc, m = ngx_conf_parse(new_cf, full_name)
    if rc == nil then
        return nil, m
    end

    return OK
end


http_server_name = function(cf, cmd)
    local directive = {}
    directive.ARGS = cf.args

    cf.ngx_conf[#cf.ngx_conf + 1] = directive
    -- save index in ngx_conf
    directive.INDEX = #cf.ngx_conf
    directive.PARENT = cf.ngx_conf

    local parse_conf = {}
    parse_conf.names = {}
    directive.PARSE_CONF = parse_conf

    local server_block = cf.ngx_conf
    if server_block.INCLUDE then
        return nil, "not support server_name included in a file alone"
    end

    if server_block.server_name then
        return nil, "not support,server_name is more than one in a server"
    end

    for i, sn in ipairs(cf.args) do
        if i > 1 then
            parse_conf.names[sn] = 1
        end
    end

    server_block.server_name = directive

    return OK
end


http_server_listen = function(cf, cmd)
    local directive = {}
    directive.ARGS = cf.args

    cf.ngx_conf[#cf.ngx_conf + 1] = directive
    -- save index in ngx_conf
    directive.INDEX = #cf.ngx_conf
    directive.PARENT = cf.ngx_conf

    -- parse
    local parse_conf = {}
    parse_conf.port = cf.args[2]
    if not string.match(parse_conf.port, "^%d+$") then
        return nil, "not support this format of directive listen "..cf.args[2]
    end
    for _, arg in ipairs(cf.args) do
       if arg == "default" or arg == "default_server" then
           parse_conf.default = 1
       end
    end

    directive.PARSE_CONF = parse_conf

    local server_block = cf.ngx_conf
    if server_block.INCLUDE then
        return nil, "not support directive listen included in a file alone"
    end

    if server_block.listen then
        return nil, "not support,directive listen is more than one in a server"
    end

    server_block.listen = directive

    return OK
end


http_proxy_pass = function(cf, cmd)
    local directive = {}
    directive.ARGS = cf.args

    cf.ngx_conf[#cf.ngx_conf + 1] = directive
    -- save index in ngx_conf
    directive.INDEX = #cf.ngx_conf
    directive.PARENT = cf.ngx_conf

    -- parse
    local parse_conf = {}
    parse_conf.host = cf.args[2]

    directive.PARSE_CONF = parse_conf

    local loc_or_lif_block = cf.ngx_conf
    if loc_or_lif_block.INCLUDE then
        return nil, "not support proxy_pass included in a file alone"
    end

    loc_or_lif_block.proxy_pass = directive

    return OK
end


http_rewrite_set = function(cf, cmd)
    local directive = {}
    directive.ARGS = cf.args

    cf.ngx_conf[#cf.ngx_conf + 1] = directive
    -- save index in ngx_conf
    directive.INDEX = #cf.ngx_conf
    directive.PARENT = cf.ngx_conf

    local ngx_conf = cf.ngx_conf
    if ngx_conf.INCLUDE and not ngx_conf.PARENT.SERVER then
        return nil, "not support set (in location or if) can not included in a file alone"
    end

    -- new sets table
    if ngx_conf.sets == nil then
        ngx_conf.sets = {}
    end

    -- parse
    local parse_conf = {}
    parse_conf.var_name = cf.args[2]
    parse_conf.value = cf.args[3]

    directive.PARSE_CONF = parse_conf

    ngx_conf.sets[#ngx_conf.sets + 1] = directive

    return OK
end


-- commands
local modules = {}
modules[CORE_MODULE] = {}
modules[HTTP_MODULE] = {}
--modules[CONF_MODULE] = {}

--local cmds = modules[CONF_MODULE]

cmds = modules[CORE_MODULE]
cmds[#cmds + 1] = {name = "http", ctype = core_main_conf, argc = CONF_NOARGS, is_block = 1, set = http_block}
cmds[#cmds + 1] = {name = "include", ctype = ANY_CONF, argc = CONF_TAKE1, set = conf_include}

cmds = modules[HTTP_MODULE]

cmds[#cmds + 1] = {name = "include", ctype = ANY_CONF, argc = CONF_TAKE1, set = conf_include}
cmds[#cmds + 1] = {name = "geo", ctype = http_main_conf, argc = CONF_TAKE12, is_block = 1, set = http_geo_block}
cmds[#cmds + 1] = {name = "server", ctype = http_main_conf, argc = CONF_NOARGS, is_block = 1, set = http_server_block}
cmds[#cmds + 1] = {name = "listen", ctype = http_srv_conf, argc = CONF_1MORE, set = http_server_listen}
cmds[#cmds + 1] = {name = "server_name", ctype = http_srv_conf, argc = CONF_1MORE, set = http_server_name}
cmds[#cmds + 1] = {name = "location", ctype = http_srv_loc_conf, argc = CONF_TAKE12, is_block = 1, set = http_location_block}
cmds[#cmds + 1] = {name = "set", ctype = http_srv_sif_loc_lif_conf, argc = CONF_TAKE2, set = http_rewrite_set}
cmds[#cmds + 1] = {name = "if", ctype = http_srv_loc_conf, argc = CONF_1MORE, is_block = 1, set = http_rewrite_if}
cmds[#cmds + 1] = {name = "proxy_pass", ctype = http_loc_lif_conf, argc = CONF_TAKE1, set = http_proxy_pass}
cmds[#cmds + 1] = {name = "upstream", ctype = http_main_conf, argc = CONF_TAKE1, is_block = 1, set = http_upstream_block}
--cmds[#cmds + 1] = {name = "server", ctype = http_ups_conf, argc = CONF_1MORE, set = http_upstream_server}


local function conf_read_token(cf)
    local found = nil
    local need_space = nil
    local last_space = 1;
    local sharp_comment = nil
    local variable = nil
    local quoted = nil
    local s_quoted = nil
    local d_quoted = nil

    local b = cf.conf_file.buffer;
    local start = b.pos;
    local start_line = cf.conf_file.line;
    cf.args = {}

    while true do
        repeat

        if b.pos >= b.last then 
            if #cf.args > 0 then
                return ERROR, "unexpected end of file, expecting ; or }";
            end
            return CONF_FILE_DONE
        end

        len = b.pos - start;

        if len >= CONF_BUFFER then 
            cf.conf_file.line = start_line;

            if d_quoted then 
                ch = '"';

            elseif s_quoted then
                ch = '\'';

            else 
                return ERROR, "too long parameter "..string.sub(b.body, start, start + 10).." started"
            end 

            return ERROR, "too long parameter, probably missing terminating "..char_map[ch].." character"
        end

        ch = string.byte(b.body, b.pos)
        b.pos = b.pos + 1

        if ch == LF then
            cf.conf_file.line = cf.conf_file.line + 1

            if sharp_comment then 
                sharp_comment = nil
            end
        end

        if sharp_comment then
            break
        end

        if quoted then
            quoted = nil
            break
        end

        if need_space then
            if ch == SPACE or ch == TAB or ch == CR or ch == LF then
                last_space = 1;
                need_space = nil;
                break
            end
            

            if ch == SEMI then
                return OK;
            end

            if ch == LBRACE then
                return CONF_BLOCK_START;
            end

            if ch == RBRACKET then
                last_space = 1
                need_space = nil
            else 
                 return ERROR, "unexpected char "..char_map[ch]
            end
        end

        if last_space then
            if ch == SPACE or ch == TAB or ch == CR or ch == LF then
                break
            end

            start = b.pos - 1;
            start_line = cf.conf_file.line;

            if ch == SEMI or ch == LBRACE then
                if #cf.args == 0 then
                    return ERROR, "unexpected char "..char_map[ch]
                end

                if ch == LBRACE then
                    return CONF_BLOCK_START;
                end

                return OK;
            elseif ch == RBRACE then
                if #cf.args ~= 0 then
                    return ERROR, "unexpected char }"
                end

                return CONF_BLOCK_DONE;
            elseif ch == SHARP then
                sharp_comment = 1;
                break

            elseif ch == QUOTE then
                quoted = 1;
                last_space = nil
                break

            elseif ch == DQUOTE then
                -- start = start + 1
                d_quoted = 1;
                last_space = nil
                break

            elseif ch == SQUOTE then
                -- start = start + 1
                s_quoted = 1;
                last_space = nil
                break
            else
                last_space = nil
            end

        else
            if ch == LBRACE and variable then
                break
            end

            variable = nil

            if ch == QUOTE then
                quoted = 1;
                break
            end

            if ch == DOLLAR then
                variable = 1;
                break
            end

            if d_quoted then
                if ch == DQUOTE then
                    d_quoted = nil
                    need_space = 1;
                    found = 0
                end

            elseif s_quoted then
                if ch == SQUOTE then
                    s_quoted = nil
                    need_space = 1;
                    found = 0
                end

            elseif ch == SPACE or ch == TAB or ch == CR or ch == LF
                     or ch == SEMI or ch == LBRACE then
                last_space = 1;
                found = 1;
            end

            if found then
                cf.args[#cf.args + 1] = string.sub(b.body, start, b.pos - found - 1)
                if ch == SEMI then
                    return OK;
                end

                if ch == LBRACE then
                    return CONF_BLOCK_START;
                end

                found = nil
            end
        end
        until true
    end
end


local function conf_default_handler(cf, rc)
    for _, cmd in ipairs(modules[cf.module_type]) do
        if cmd.name == cf.args[1] then
            if (cmd.ctype == ANY_CONF and include_type[cf.cmd_type] and include_file_not_ignored(cf.args[2]))
               or cmd.ctype[cf.cmd_type] then
                if cmd.is_block == nil and rc ~= OK then
                    return nil, string.format("directive %s is not terminated by ;", cmd.name)
                end
                if cmd.is_block and rc ~= CONF_BLOCK_START then
                    return nil, string.format("directive %s has no opening {", cmd.name)
                end
                if cmd.argc ~= CONF_ANY then
                    if cmd.argc == CONF_FLAG then
                        if #cf.args ~= 2 then
                            return nil, string.format("invalid number of arguments in \"%s\" directive", cmd.name)
                        end

                    elseif cmd.argc == CONF_1MORE then

                        if #cf.args < 2 then
                            return nil, string.format("invalid number of arguments in \"%s\" directive", cmd.name)
                        end

                    elseif cmd.argc == CONF_2MORE then

                        if #cf.args < 3 then
                            return nil, string.format("invalid number of arguments in \"%s\" directive", cmd.name)
                        end

                    elseif #cf.args > CONF_MAX_ARGS then

                        return nil, string.format("invalid number of arguments in \"%s\" directive", cmd.name)

                    else
                        local found
                        for _, argc in ipairs(argument_number[#cf.args]) do
                            if cmd.argc == argc then
                                found = 1
                                break
                            end
                        end
                        if not found then
                            return nil, string.format("invalid number of arguments in \"%s\" directive", cmd.name)
                        end
                    end
                end

                local rv, m = cmd.set(cf, cmd)

                if rv == OK then return OK end
                if rv == nil then return nil, m end
                return nil, string.format("set command %s failed", cmd.name)
            end
        end
    end

    -- unknown directives, just record it
    local directive = {}
    directive.ARGS = cf.args
    directive.CTYPE = CONF_UNKNOWN

    cf.ngx_conf[#cf.ngx_conf + 1] = directive

    if rc == CONF_BLOCK_START then
        directive.BLOCK = {}
        local module_type = cf.module_type
        local cmd_type = cf.cmd_type
        local ngx_conf = cf.ngx_conf
        cf.cmd_type = CONF_UNKNOWN
        cf.ngx_conf = directive.BLOCK
        local r, m = ngx_conf_parse(cf)
        if not r then return nil, m end
        cf.cmd_type = cmd_type
        cf.ngx_conf = ngx_conf
    end

    return OK
end


ngx_conf_parse = function(cf, cf_file_name)
    local parse_type
    local rc, rv, f, m
    if cf_file_name then
        f, m = io.open(cf_file_name, "r")
        if f == nil then return nil, m end

        if cf.conf_file == nil then cf.conf_file = {} end
        if cf.conf_file.buffer == nil then cf.conf_file.buffer = {} end
        cf.conf_file.line = 1

        cf.conf_file.buffer.body = f:read("*all")
        cf.conf_file.buffer.pos = 1
        cf.conf_file.buffer.last = #cf.conf_file.buffer.body

        f:close()
        parse_type = parse_file
    else
        parse_type = parse_block
    end

    while true do
        rc, m = conf_read_token(cf)

        if rc == ERROR then
            return nil, m
        end

        if rc == CONF_FILE_DONE then
            if parse_type == parse_block then
                return nil,  "unexpected end of file, expecting }"
            end
            return OK
        end

        if rc == CONF_BLOCK_DONE then
            if parse_type ~= parse_block then
                return nil, "unexpected }"
            end
            return OK
        end

        rc, m = conf_default_handler(cf, rc)

        if rc == nil then return nil, m end
    end

    return nil, "unknown reason"
end



local function ngx_conf_to_file(ngx_conf, file_name, success_created_files)
    local function ngx_conf_write_to_file(ngx_conf, blanks, f)
        if ngx_conf == nil then return OK end
        local r, m = ngx_conf_write_to_file(ngx_conf.HEAD, blanks, f)
        if r == nil then return nil, m end
        for j, directive in ipairs(ngx_conf) do
            if not directive.DELETE then
                local line = blanks
                for i, arg in ipairs(directive.ARGS) do
                    if i ~= #directive.ARGS then
                        line = line..arg.." "
                    else
                        line = line..arg
                    end
                end
                if directive.BLOCK then
                    f:write(string.format("%s {\n", line))
                    local r, m = ngx_conf_write_to_file(directive.BLOCK, "    "..blanks, f)
                    if r == nil then return nil, m end
                    f:write(string.format("%s}\n\n", blanks))
                elseif directive.NGX_CONF and directive.RECREATE then
                    f:write(string.format("%s;\n", line))
                    local r, m = ngx_conf_to_file(directive.NGX_CONF, directive.ARGS[2], success_created_files)
                    if r == nil then return nil, m end
                else
                    f:write(string.format("%s;\n", line))
                end
            end
            local r, m = ngx_conf_write_to_file(directive.NEXT, blanks, f)
            if r == nil then return nil, m end
        end
        local r, m = ngx_conf_write_to_file(ngx_conf.TAIL, blanks, f)
        if r == nil then return nil, m end
        return OK
    end

    local f, m = io.open(file_name, "w")
    if f == nil then return nil, m end
    if success_created_files then success_created_files[file_name] = 1 end
    local r, m = ngx_conf_write_to_file(ngx_conf, "", f)
    if r == nil then f:close(); return nil, m end
    f:close()
    return OK
end


local function get_server_name_map(ngx_conf_servers)
    for _, server in ipairs(ngx_conf_servers) do
        if server.server_name then
            local port
            if server.listen then
                port = server.listen.PARSE_CONF.port
            else
                port = "80"
            end
            local unique_name_map = {}
            -- save the parent block of this server_name
            for sn in pairs(server.server_name.PARSE_CONF.names) do
                local sn_with_port = sn..":"..port
                ngx_conf_server_name_map[sn_with_port] = server
                unique_name_map[sn] = sn_with_port
            end
            -- get unique name
            server.UNIQUE_NAME_MAP = unique_name_map
        end
    end
end


local function get_loc_blocks(ngx_conf)
    local num = 0
    local num_of_proxy_pass = 0
    local num_of_ifs = 0
    if ngx_conf.ifs ~= nil then
        num_of_ifs = #ngx_conf.ifs
    end
    if ngx_conf.locations ~= nil then
        num = #ngx_conf.locations
        for _, loc in ipairs(ngx_conf.locations) do
            local n1, n2, n3 = get_loc_blocks(loc) 
            num = num + n1
            num_of_proxy_pass = num_of_proxy_pass + n2
            num_of_ifs = num_of_ifs + n3
            -- proxy_pass in location
            if loc.proxy_pass then 
                num_of_proxy_pass = num_of_proxy_pass + 1
            end
            -- proxy_pass in if block in location
            if loc.ifs then
                for _, lif in ipairs(loc.ifs) do
                    if lif.proxy_pass then
                        num_of_proxy_pass = num_of_proxy_pass + 1
                    end
                end
            end
        end
    end
    if ngx_conf.includes ~= nil then
        for _, inc in ipairs(ngx_conf.includes) do
            local n1, n2, n3 = get_loc_blocks(inc) 
            num = num + n1
            num_of_proxy_pass = num_of_proxy_pass + n2
            num_of_ifs = num_of_ifs + n3
        end
    end
    return num, num_of_proxy_pass, num_of_ifs
end


local function print_conf_checksum(ngx_conf_checksum)
    ngx.print("==configure checksum==\n")
    ngx.print(string.format("http incs:%d number of servers:%d number of sns:%d\n", ngx_conf_checksum.http.num_of_incs,
              ngx_conf_checksum.server.num, ngx_conf_checksum.server.num_of_sns))

    for sn, s in pairs(ngx_conf_checksum.server.servers) do
        ngx.print(string.format("server %s:\n", sn))
        ngx.print(string.format("incs(%d), ifs(%d), loc(%d), pps(%d) sns(%d)\n", s.num_of_incs, s.num_of_ifs,
                                s.num_of_locs, s.num_of_pps, s.num_of_sns))
    end
    ngx.print("==end of checksum==\n")
end


local function calculate_conf_checksum()
    -- init checksum
    local ngx_conf_checksum = {}
    ngx_conf_checksum.http = {}
    ngx_conf_checksum.server = {}

    local function get_include_lines(ngx_conf)
        local num = 0
        if ngx_conf.includes == nil then return num end
        num = #ngx_conf.includes
        for _, inc in ipairs(ngx_conf.includes) do
           num = num + get_include_lines(inc) 
        end
        return num
    end

    local function get_num_of_sns(ngx_conf)
        if ngx_conf.server_name then
            return #ngx_conf.server_name.ARGS - 1
        else
            return 0
        end
    end

    -- number of include lines in http block(not in server)
    ngx_conf_checksum.http.num_of_incs = get_include_lines(ngx_conf_http)

    -- number of servers
    ngx_conf_checksum.server.num = #ngx_conf_servers

    -- number of server_names and locs,pps,incs(not in loc) in each server
    local num = 0
    local servers = {}

    for k, v in pairs(ngx_conf_server_name_map) do
        num = num + 1
        local server_checksum = {}
        servers[k] = server_checksum
        server_checksum.num_of_sns = get_num_of_sns(v)
        server_checksum.num_of_incs = get_include_lines(v)
        server_checksum.num_of_locs, server_checksum.num_of_pps, server_checksum.num_of_ifs = get_loc_blocks(v)
    end

    ngx_conf_checksum.server.num_of_sns = num

    ngx_conf_checksum.server.servers = servers

    return ngx_conf_checksum
end


local function update_conf_checksum(ngx_conf_checksum, dowhat, data)
    -- init to stop/start or test
    if dowhat == "init" then
        local servers = ngx_conf_checksum.server.servers
        local jservers = data
        local num_of_split = 0
        
        for _, server in ipairs(ngx_conf_servers) do
            if server.server_name then
                local names = {}
                local gray_names = {}
                local num = 0
                for name in pairs(server.server_name.PARSE_CONF.names) do
                    name = server.UNIQUE_NAME_MAP[name]
                    if jservers.names[name] then    
                        num = num + 1
                        gray_names[#gray_names + 1] = name
                    else
                        names[#names + 1] = name
                    end
                end
                if num > 0 then
                    for _, name in ipairs(gray_names) do
                        -- number of server name
                        servers[name].num_of_sns = num
                    end
                    for _, name in ipairs(names) do
                        -- number of server name
                        servers[name].num_of_sns = servers[name].num_of_sns - num
                    end
                    -- split?
                    if #names > 0 then
                        num_of_split = num_of_split + 1
                    end
                end
            end
        end

        -- number of splitted server
        ngx_conf_checksum.server.num = ngx_conf_checksum.server.num + num_of_split

        -- number of include lines in http(not in server)
        ngx_conf_checksum.http.num_of_incs = ngx_conf_checksum.http.num_of_incs + 1

        for name in pairs(jservers.names) do
            -- error server name in jservers
            if servers[name] == nil then
                return nil, "unknown host "..name
            end

            -- number of include lines in server(not in location)
            servers[name].num_of_incs = servers[name].num_of_incs + 1

            -- number of locations and proxy_pass
            if jservers.conf.uris then
                servers[name].num_of_locs = servers[name].num_of_locs + #jservers.conf.uris
                servers[name].num_of_pps = servers[name].num_of_pps + #jservers.conf.uris
            end

            -- tair location
            if string.match(jservers.conf.fcm, "nick") then
                servers[name].num_of_locs = servers[name].num_of_locs + 1
            end
        end
    end
    if dowhat == "clear" then
        local sid = data
        local servers = ngx_conf_checksum.server.servers
        -- number of include lines in http(not in server)
        ngx_conf_checksum.http.num_of_incs = ngx_conf_checksum.http.num_of_incs - 1

        local srv_inc_file  = "gray_"..sid.."_srv.conf" 
        -- here http_inc_file will be check after this function
        for _, server in ipairs(ngx_conf_servers) do
            if server.includes then
                for _, inc in ipairs(server.includes) do
                    if inc.PARSE_CONF.file_name == srv_inc_file then
                        if server.server_name then
                            for name in pairs(server.server_name.PARSE_CONF.names) do
                                name = server.UNIQUE_NAME_MAP[name]
                                local num_of_locs, num_of_pps, num_of_ifs = get_loc_blocks(inc)
                                -- number of locations and proxy_pass
                                servers[name].num_of_locs = servers[name].num_of_locs - num_of_locs 
                                servers[name].num_of_pps = servers[name].num_of_pps - num_of_pps
                                -- number of include lines in server(not in location)
                                servers[name].num_of_incs = servers[name].num_of_incs - 1
                            end
                        end
                    end
                end
            end
        end
    end

    --print_conf_checksum(ngx_conf_checksum)
end


local function verify_conf_checksum(ngx_conf_name, ngx_conf_checksum)
    -- reinit 
    global_cf.module_type = CORE_MODULE
    global_cf.cmd_type = MAIN_CONF
    global_cf.conf_file = {}
    global_cf.conf_file.buffer = {}
    global_cf.ngx_conf = {}

    ngx_conf_server_name_map = {}
    ngx_conf_ups_name_map = {}

    ngx_conf_http = nil
    ngx_conf_servers = {}
    ngx_conf_upstreams = {}
    ngx_conf_geos = {}

    -- parse nginx configure file
    local r, m = ngx_conf_parse(global_cf, ngx_conf_name)
    if r == nil then
        return nil, "parse new configure file failed\n"..m
    end

    get_server_name_map(ngx_conf_servers)

    local new_conf_checksum = calculate_conf_checksum()

    --print_conf_checksum(new_conf_checksum)

    -- verify
    if new_conf_checksum.http.incs ~= ngx_conf_checksum.http.incs then
        return nil, "number of include files in new configure file is wrong"
    end

    if new_conf_checksum.server.num ~= ngx_conf_checksum.server.num then
        return nil, "number of server in new configure file is wrong"
    end

    if new_conf_checksum.server.num_of_sns ~= ngx_conf_checksum.server.num_of_sns then
        return nil, "number of server names in new configure file is wrong"
    end

    local new_servers = new_conf_checksum.server.servers

    for sn, server in pairs(ngx_conf_checksum.server.servers) do
        if new_servers[sn] == nil then
            return nil, "host "..sn.." not defined in new configure file"
        end
        if new_servers[sn].num_of_sns ~= server.num_of_sns then 
            return nil, sn..": number of server names in new configure file is wrong"
        end
        if new_servers[sn].num_of_locs ~= server.num_of_locs then 
            return nil, sn..": number of locations in new configure file is wrong"
        end
        if new_servers[sn].num_of_incs ~= server.num_of_incs then 
            return nil, sn..": number of include files in new configure file is wrong"
        end
        if new_servers[sn].num_of_pps ~= server.num_of_pps then 
            return nil, sn..": number of proxy_pass in new configure file is wrong"
        end
        if new_servers[sn].num_of_ifs ~= server.num_of_ifs then 
            return nil, sn..": number of if in new configure file is wrong"
        end
    end

    return OK
end


-- only for server directive
local function copy_directive(d)
    if d == nil then return nil end
    local nd = {}
    nd.CTYPE = d.CTYPE
    nd.ARGS = {}
    for _, arg in ipairs(d.ARGS) do
        table.insert(nd.ARGS, arg)
    end
    if d.BLOCK then
        nd.BLOCK = {}
        for _, directive in ipairs(d.BLOCK) do
            table.insert(nd.BLOCK, copy_directive(directive))
        end
        nd.BLOCK.INDEX = d.BLOCK.INDEX
    end
    if d.NGX_CONF then
        nd.NGX_CONF = {}
        for _, directive in ipairs(d.NGX_CONF) do
            table.insert(nd.NGX_CONF, copy_directive(directive))
        end
        nd.NGX_CONF.INDEX = d.NGX_CONF.INDEX
    end
    local function copy_xxs(src, dst, xxs, yy)
        if src[xxs] then 
            if #src[xxs] > 0 then
                dst[xxs] = {}
                for _, xx in ipairs(src[xxs]) do
                    if yy then
                        table.insert(dst[xxs], dst[xx.INDEX][yy])
                        dst[xx.INDEX][yy].PARSE_CONF = xx.PARSE_CONF
                        dst[xx.INDEX][yy].INDEX = xx.INDEX
                        dst[xx.INDEX][yy].PARENT = dst
                    else
                        table.insert(dst[xxs], dst[xx.INDEX])
                        dst[xx.INDEX].PARSE_CONF = xx.PARSE_CONF
                        dst[xx.INDEX].INDEX = xx.INDEX
                        dst[xx.INDEX].PARENT = dst
                    end
                end
            else
                dst[xxs] = dst[src[xxs].INDEX]
                dst[xxs].PARSE_CONF = src[xxs].PARSE_CONF
                dst[xxs].INDEX = src[xxs].INDEX
                dst[xxs].PARENT = dst
            end
        end
    end
    if d.CTYPE == HTTP_SRV_CONF then
        copy_xxs(d.BLOCK, nd.BLOCK, "ifs",       "BLOCK")
        copy_xxs(d.BLOCK, nd.BLOCK, "includes",  "NGX_CONF")
        copy_xxs(d.BLOCK, nd.BLOCK, "locations", "BLOCK")
    elseif d.CTYPE == HTTP_LOC_CONF then
        copy_xxs(d.BLOCK, nd.BLOCK, "locations", "BLOCK")
        copy_xxs(d.BLOCK, nd.BLOCK, "ifs",       "BLOCK")
        copy_xxs(d.BLOCK, nd.BLOCK, "proxy_pass")
        copy_xxs(d.BLOCK, nd.BLOCK, "sets")
    elseif d.CTYPE == HTTP_LIF_CONF then
        copy_xxs(d.BLOCK, nd.BLOCK, "sets")
        copy_xxs(d.BLOCK, nd.BLOCK, "proxy_pass")
    elseif d.CTYPE == CONF_INCLUDE then
        copy_xxs(d.NGX_CONF, nd.NGX_CONF, "ifs",       "BLOCK")
        copy_xxs(d.NGX_CONF, nd.NGX_CONF, "includes",  "NGX_CONF")
        copy_xxs(d.NGX_CONF, nd.NGX_CONF, "locations", "BLOCK")
    end

    return nd
end


local function ngx_conf_directive_insert(directive, new_directive)
    if directive.NEXT == nil then directive.NEXT = {} end
    table.insert(directive.NEXT, new_directive)
end


local function ngx_conf_insert_head(ngx_conf, directive)
    if ngx_conf.HEAD == nil then
        ngx_conf.HEAD = {}
    end
    table.insert(ngx_conf.HEAD, directive)
end
 

local function ngx_conf_insert_tail(ngx_conf, directive)
    if ngx_conf.TAIL == nil then
        ngx_conf.TAIL = {}
    end
    table.insert(ngx_conf.TAIL, directive)
end

--------------------FUNCTIONS FOR A/B TEST--------------------------
local function create_http_gconf_file(fn, js)
    local f, m = io.open(fn, "w")

    if not f then return nil, m end

    -- upstream a block
    f:write(string.format("upstream gray_%s_sa {\n", js.sid))

    if js.lbm == 'iphash' then
        f:write("    ip_hash;\n")
    end

    if js.hc then
        for _, a in ipairs(js.asa) do
            f:write("    server ", a, " max_fails=0;\n")
        end

        f:write(string.format("    check interval=%s rise=%s fall=%s timeout=%s default_down=%s type=http;\n",
                js.hc.interval, js.hc.rise, js.hc.fall, js.hc.timeout,
                js.hc.default_down))
        f:write(string.format("    check_http_send \"GET %s HTTP/1.0\\r\\n\\r\\n\";\n",
                js.hc.uri))
        f:write(string.format("    check_http_expect_alive %s;\n", js.hc.expect_codes))
    else
        for _, a in ipairs(js.asa) do
            f:write("    server ", a, ";\n")
        end
    end

    f:write("}\n\n")

    -- upstream b block
    f:write(string.format("upstream gray_%s_sb {\n", js.sid))
    if js.lbm == 'iphash' then
        f:write("    ip_hash;\n")
    end
    if js.hc then
        for _, a in ipairs(js.bsa) do
            f:write("    server ", a, " max_fails=0;\n")
        end

        if js.lbm == 'rrb' then
            for _, a in ipairs(js.asa) do
                f:write("    server ", a, " max_fails=0 backup;\n")
            end
        end

        f:write(string.format("    check interval=%s rise=%s fall=%s timeout=%s default_down=%s type=http;\n",
                js.hc.interval, js.hc.rise, js.hc.fall, js.hc.timeout,
                js.hc.default_down))
        f:write(string.format("    check_http_send \"GET %s HTTP/1.0\\r\\n\\r\\n\";\n",
                js.hc.uri))
        f:write(string.format("    check_http_expect_alive %s;\n", js.hc.expect_codes))
    else
        for _, a in ipairs(js.bsa) do
            f:write("    server ", a, ";\n")
        end

        if js.lbm == 'rrb' then
            for _, a in ipairs(js.asa) do
                f:write("    server ", a, " backup;\n")
            end
        end
    end

    f:write("}\n\n")

    -- geo block
    if string.match(js.fcm, "ip") then
        if js.ips or js.geo then
            f:write(string.format("geo $gray_%s_ip {\n    ranges;\n    default 0;\n", js.sid))
            if js.ips then
                for _, a in ipairs(js.ips) do
                    f:write("    ", a, " 1;\n")    
                end
            end

            if js.geo then
                for _, city in ipairs(js.geo) do
                    f:write(string.format("    include "..gray_conf_name..gray_res_dir.."geo_%s.conf;\n", city))
                end
            end
            f:write("}\n")
        end
    end
    f:close()
    return OK
end


--- Create gray configure file included in server block
-- @param fn The gray server configure file name 
-- @param js The decoded json string 
-- @param sw The gray switch 
-- @return Create a configure file(include gray_switch, gray confs, gray locations, tair location)
-- OK on success, nil and error message on error
local function create_srv_gconf_file(fn, js, sw)
    local f, m = io.open(fn, "w")
    if not f then return nil, m end

    f:write("gray_switch ", sw, ";\n")
    f:write("gray_strategy_id ", js.sid, ";\n")
    f:write("gray_flow_control_method ", js.fcm, ";\n")

    local sa = "gray_"..js.sid.."_sa"
    local sb = "gray_"..js.sid.."_sb"
    f:write("gray_a_server_name ", sa, ";\n")
    f:write("gray_b_server_name ", sb, ";\n")

    if string.match(js.fcm, "mod") then
        f:write("gray_mod_cookie_name ", js.mdck.name, ";\n")
        f:write(string.format("gray_mod %s %s %s;\n", js.mdck.max, js.mdck.cond, js.mdck.value))
    end

    if string.match(js.fcm, "nick") then
        f:write("gray_nick_cookie_name ", js.nick.name, ";\n")
        f:write("gray_tair_loc_name /gray_tair_", js.sid, "_loc;\n")
        f:write("gray_tair_namespace ", js.tns, ";\n")
    end

    if string.match(js.fcm, "ip") then 
        if js.ips or js.geo then
            f:write("gray_geo_var_name ", "gray_", js.sid, "_ip;\n")
            f:write("gray_geo_ip_value 1;\n")
        end
    end
    
    if js.abck then
        f:write("gray_ab_cookie_name ", js.abck.name, ";\n")
        f:write("gray_ab_cookie_pos ", js.abck.index, ";\n")
        f:write("gray_ab_cookie_valid_value ", js.abck.b, ";\n")
        f:write("gray_ab_cookie_invalid_value ", js.abck.a, ";\n")
        f:write("gray_ab_cookie_unconcerned_value ", js.abck.o, ";\n")
        local suffix = "\"; "
        if js.abck.domain then suffix=suffix.."Domain="..js.abck.domain.."; " end
        suffix=suffix.."Path="..js.abck.path.."\""
        f:write("gray_cookie_suffix ", suffix, ";\n")
        f:write("gray_cookie_expires ", js.abck.expire, ";\n")
    end
     
    f:write("\n")

    -- locations
    if js.uris then
        for _, u in ipairs(js.uris) do
            if u ~= "" then
                f:write(string.format("location = %s {\n    gray on;\n    proxy_pass http://$gray_server_name;\n}\n\n", u))
            end
        end
    else 
        f:write("gray on;\n")
    end
    
    -- tair location
    if string.match(js.fcm, "nick") then
        local l = "location = /gray_tair_"..js.sid.."_loc " 
        l = l .. [[
{
    internal;
    tair_java on;
    tair_methods get;
    set $tair_key $arg_key;
    set $tair_method $arg_method;
    set $tair_area $arg_area;
    set $tair_value $arg_value;
    set $tair_version $arg_version;
    set $tair_exptime $arg_exptime;
    set $tair_count $arg_count;
    set $tair_value_type $arg_type;
    set $tair_init_value $arg_init;
]]
        l = l.."    tair_pass " 
        for _, u in ipairs(js.tsa) do
            l = l..u.." "
        end
        l = l..js.tgroup
        l = l..";\n}\n"

        if js.tct == 'c' then 
            l = string.gsub(l, "tair_java on;","tair_java off;")
        end
        f:write(l)
    end

    f:close()

    return OK
end


--- Decode and check the format of a json string
-- @param json_str The json string 
-- @return The decoded json table on success, nil and error message on error
local function check_json_format(json_str)
    local gray = {}
    local fcm_map = {}
    local tct_map = {}
    local lbm_map = {}

    fcm_map.ip = 1
    fcm_map.nick = 1
    fcm_map.mod = 1
    fcm_map.ip_or_mod = 1
    fcm_map.ip_or_nick = 1
    fcm_map.ip_and_nick = 1
    fcm_map.mod_or_nick = 1
    fcm_map.ip_or_mod_or_nick = 1

    tct_map.c = 1
    tct_map.java = 1

    lbm_map.rrb = 1
    lbm_map.iphash = 1

    gray.fcm = 'nick'
    gray.tct = 'java'
    gray.lbm = 'rrb'
    --gray.abck = { name = "ab", a = "0", b = "1", o = "-", index = "0", path = "/", expire = "3d"};
    gray.mdck = { name = "_mod_", max = "2", value = "0", cond = "==" }
    gray.nick = { name = "_nk_" }
    gray.hc = { interval = "3000", rise = "1", fall = "2", timeout = "5000",
                default_down = "false", uri = "/status.taobao", expect_codes = "http_2xx" }

    -- JSON decode
    local r, v = pcall(json.decode, json_str)
    if not r then return nil, "decode json failed"  end

    -- check format
    if type(v) ~= 'table'        then return nil, "wrong format"   end 

    if not icheck(v.sid, 'string')   then return nil, "no strategy id or wrong format"   end 
    if string.match(v.sid, "_")      then return nil, "invalid char _ in strategy id"    end
    if not icheck(v.hosts, 'string') then return nil, "no server name or wrong format"   end 

    if v.lbm == nil                then v.lbm = gray.lbm end
    if not icheck(v.lbm, 'string') then return nil, "wrong format of load balance method" end 
    if lbm_map[v.lbm] == nil       then return nil, "unknown type of load balance" end 

    if not icheck(v.asa, 'array') then return nil, "no A server address or wrong format" end
    if not icheck(v.bsa, 'array') then return nil, "no B server adderss or wrong format" end

    if v.fcm == nil                then v.fcm = gray.fcm          end
    if not icheck(v.fcm, 'string') then return nil, "wrong format of flow control method" end
    if fcm_map[v.fcm] == nil       then return nil, "unknown flow control method" end 

    if v.uris ~= nil then
        if not icheck(v.uris, 'array') then return nil, "wrong format of uri list" end 
    end

    if v.hc == "off" then
        v.hc = nil
    elseif v.hc == nil then
        v.hc = gray.hc
    else
        if not icheck(v.hc, 'table') then return nil, "wrong format of health check" end 
    end

    if v.hc then
       if v.hc.interval == nil then v.hc.interval = gray.hc.interval end
       if v.hc.rise == nil then v.hc.rise = gray.hc.rise end
       if v.hc.fall == nil then v.hc.fall = gray.hc.fall end
       if v.hc.timeout == nil then v.hc.timeout = gray.hc.timeout end
       if v.hc.timeout < "1000" then return nil, "too small timeout for health check" end
       if v.hc.default_down == nil then v.hc.default_down = gray.hc.default_down end
       if v.hc.uri == nil then v.hc.uri = gray.hc.uri end
       if v.hc.expect_codes == nil then v.hc.expect_codes = gray.hc.expect_codes end
    end

    if v.abck ~= nil then
        if not icheck(v.abck, 'table')     then return nil, "wrong format of ab-cookie" end 

        if v.abck.a == nil      then v.abck.a = "0"       end
        if v.abck.b == nil      then v.abck.b = "1"       end
        if v.abck.o == nil      then v.abck.o = "-"       end
        if v.abck.name == nil   then v.abck.name = "ab"   end
        if v.abck.path == nil   then v.abck.path = "/"    end
        if v.abck.index == nil  then v.abck.index = "0"   end
        if v.abck.expire == nil then v.abck.expire = "3d" end
    end

    if string.match(v.fcm, "mod") then
        if v.mdck == nil  then v.mdck = gray.mdck end
        if not icheck(v.mdck, 'table') then return nil, "wrong format of mod-cookie" end 
        if v.mdck.max   == nil  then v.mdck.max = "2"      end
        if v.mdck.name  == nil  then v.mdck.name = "_mod_" end
        if v.mdck.cond  == nil  then v.mdck.cond = "=="    end
        if v.mdck.value == nil  then v.mdck.value = "0"    end
    end

    if string.match(v.fcm, 'nick') then
        if not v.nick then v.nick = gray.nick end     
        if not icheck(v.nick, 'table') then return nil, "wrong format of nick-cookie"  end 

        if v.nick.name == nil then v.nick.name = "_nk_" end

        if v.tct == nil                then v.tct = gray.tct end
        if not icheck(v.tct, 'string') then return nil, "wrong format of tair client type" end 
        if tct_map[v.tct] == nil       then return nil, "unknown type of tair client" end 

        if not icheck(v.tsa, 'array')     then return nil, "no tair server address" end 
        if not icheck(v.tgroup, 'string') then return nil, "no tair group or wrong format"  end 
        if not icheck(v.tns, 'string')    then return nil, "no tair namespace or wrong format" end 
    end

    if string.match(v.fcm, "ip") then
        if v.ips and not icheck(v.ips, 'array') then return nil, "wrong format of ip list"  end 
        if v.geo and not icheck(v.geo, 'array') then return nil, "wrong format of geo"  end 

        if v.ips then
            for _, a in ipairs(v.ips) do
                if not string.match(a, "%S+%-%S+") then
                    return nil, "wrong format of "..a.." in ip list"
                end
            end
        end

        if v.geo then
            for _, city in ipairs(v.geo) do
                local f = io.open(ngx_conf_prefix..gray_conf_name..gray_res_dir.."geo_"..city..".conf", "r")
                if not f then
                    return nil, "city "..city.." is not support now"
                else
                    f:close()
                end
            end
        end
    end

    return v
end


--- parse json from file
-- @param fn The file which contents the json string 
-- @return The decoded servers table on success, nil and error message on error
local function parse_json_from(source, in_file)
    local jservers = {}
    jservers.names = jservers_map
    local json_str = source

    if in_file then
        local f, m = io.open(source, "r")
        if f == nil then return f, m end
        json_str = f:read("*all")
        f:close()
    end

    local v, m = check_json_format(json_str)
    if v == nil then return nil, m end

    -- check string format   
    local num = 0
    for name in string.gmatch(v.hosts, "%S+") do 
        if not string.match(name, "%S+:%S+") then
            name = name..":".."80"
        end
        jservers_map[name] = 1
        num = num + 1
    end

    jservers.names = jservers_map
    jservers.num = num
    jservers.sid = v.sid
    jservers.conf = v

    if jservers.sid == nil then return nil, "no sid in jscon"    end
    if jservers.num == 0   then return nil, "no host in jscon"   end

    return jservers
end


local function update_server_locations(v, gray_all, query)
    local num = 0

    local function get_original_backend_in(sets, delete)
        if sets == nil then return nil end
        for _, set in ipairs(sets) do
           if set.PARSE_CONF.var_name == "$gray_original_server_name" then 
               -- if found then delete it
               if delete then set.PARENT[set.INDEX].DELETE = 1 end
               return set.PARSE_CONF.value
           end
        end
        return nil
    end

    local function update_location(v)
        if v.proxy_pass then
            num = num + 1
            if not query then
                if gray_all then
                    if v.proxy_pass.PARSE_CONF.host ~= "http://$gray_server_name" then
                        -- insert set directive
                        local directive = {}
                        directive.ARGS = {"set", "$gray_original_server_name", v.proxy_pass.PARSE_CONF.host}
                        ngx_conf_insert_head(v, directive)
                        v[v.proxy_pass.INDEX].ARGS[2] = "http://$gray_server_name"
                        v.proxy_pass = nil
                    end
                else
                    -- recover original proxy_pass
                    if v.proxy_pass.PARSE_CONF.host == "http://$gray_server_name" then
                        local backend = get_original_backend_in(v.sets, 1)
                        if backend == nil then
                            if #v.ARGS == 3 then
                                return nil, "proxy_pass can not recover to original backend, in location:\n"..v.ARGS[2]..v.ARGS[3]
                            else
                                return nil, "proxy_pass can not recover to original backend, in location:\n"..v.ARGS[2]
                            end
                        end
                        v[v.proxy_pass.INDEX].ARGS[2] = backend
                        v.proxy_pass = nil
                    end
                end
            end
        end
        -- update nested locations
        if v.locations then
            for _, loc in ipairs(v.locations) do
                local r, m = update_location(loc)
                if not r then return r, m end
            end 
        end 
        -- update if in location
        if v.ifs then
            for _, lif in ipairs(v.ifs) do
                local r, m = update_location(lif)
                if not r then return r, m end
            end 
        end 
        return num
    end 

    -- here v is server or include file in server block
    for _, loc in ipairs(v.locations) do
        local r, m = update_location(loc)
        if not r then return r, m end
    end 
    return num
end


--- Update server blocks of nginx configure file
-- @param servers Servers Table decoded from nginx configure file 
-- @param jservers Servers Table decoded from json string 
-- @param is_test Whether dowhat is test or not 
-- @return OK on success
-- @return nil and error message on error
local function update_server_blocks(servers, jservers, is_test)
    for name in pairs(jservers.names) do
       if ngx_conf_server_name_map[name] == nil then
           return nil, "server "..name.." not defined in configure"
       end
    end

    local new_servers = {}

    for _, s in ipairs(servers) do
        local num = 0
        local names = {}

        if s.server_name then
            for name in pairs(s.server_name.PARSE_CONF.names) do
                local unique_name = s.UNIQUE_NAME_MAP[name]
                if jservers_map[unique_name] == 1 then
                    s.server_name.PARSE_CONF.names[name] = 0
                    names[name] = 1
                    jservers_map[unique_name] = 0
                    num = num + 1
                end
            end
        end

        if num > 0 then
            -- is server in include file?
            if s.PARENT.INCLUDE then
                local directive = s.PARENT.PARENT[s.PARENT.INDEX]
                local file_name = directive.ARGS[2]
                local dir, fn = string.match(file_name, "(%S*/)(%S+)") 
                if not fn then
                    fn = file_name
                    dir = ngx_conf_prefix
                else
                    if string.sub(dir, 1, 1) ~= '/' then 
                        dir = ngx_conf_prefix..dir
                    end
                end
                if is_test then dir = gray_test_fdir end
                directive.ARGS[2] = dir..fn
                gray_hinc_fns[directive.ARGS[2]] = 1 
                directive.RECREATE = 1
            end
            if num == #s.server_name.ARGS - 1 then
                s.GRAY = 1
            else
                local ns_directive = copy_directive(s.PARENT[s.INDEX])
                -- add the new directive after this server  
                ngx_conf_directive_insert(s.PARENT[s.INDEX], ns_directive)
                local ns = ns_directive.BLOCK
                ns.PARENT = s.PARENT
                ns.GRAY = 1
                -- new server_name directive for ns
                local directive = {}
                local args = {}
                table.insert(args, "server_name")
                for n in pairs(names) do
                    table.insert(args, n)
                end
                directive.ARGS = args
                ns[s.server_name.INDEX] = directive
                ns.server_name = nil
                -- new listen directive for ns
                if s.listen and s.listen.PARSE_CONF.default == 1 then
                    local directive = {}
                    local args = {}
                    table.insert(args, "listen")
                    table.insert(args, s.listen.PARSE_CONF.port)
                    directive.ARGS = args
                    ns[s.listen.INDEX] = directive
                end
                ns.listen = nil
                -- modify server_name directive for s
                args = {}
                table.insert(args, "server_name")
                for n, v in pairs(s.server_name.PARSE_CONF.names) do
                    if v == 1 then
                        table.insert(args, n)
                    end
                end
                s[s.server_name.INDEX].ARGS = args
                table.insert(new_servers, ns)
            end
        end
    end

    -- insert new servers
    for _, ns in ipairs(new_servers) do
        table.insert(servers, ns)
    end

    -- save sid to be deleted (value = 0, both gray_sid_srv|http.conf gray_sinc_sid_..)
    for _, s in ipairs(servers) do
        if s.GRAY and s.includes then
            for _, inc in ipairs(s.includes) do
                local sid = string.match(inc.PARSE_CONF.file_name, "^gray_([^_]+)_srv%.conf$")
                if sid then
                    if not is_test then
                        return nil, "the original strategy "..sid.." is not cleared"
                    end
                    s[inc.INDEX].DELETE = 1
                    server_sids[sid] = 0
                end
                local file_name = inc.PARSE_CONF.file_name
                local base_name = string.match(file_name, "%S*/gray_srv_inc_([^/]+)$")
                if base_name and string.sub(file_name, 1, 1) == '/' then
                    gray_sinc_fns[file_name] = 0
                end
            end
        end
    end
    for _, s in ipairs(servers) do
        if s.GRAY == nil and s.includes then
            for _, inc in ipairs(s.includes) do
                local sid = string.match(inc.PARSE_CONF.file_name, "^gray_([^_]+)_srv%.conf$")
                if sid then
                    server_sids[sid] = 1
                end
                local file_name = inc.PARSE_CONF.file_name
                local base_name = string.match(file_name, "%S*/gray_srv_inc_([^/]+)$")
                if base_name and string.sub(file_name, 1, 1) == '/' then
                    gray_sinc_fns[file_name] = 1
                end
            end
        end
    end

    if server_sids[jservers.sid] then
        return nil, "the original strategy "..jservers.sid.." is not cleared"
    end

    local gray_all
    if jservers.conf.uris == nil then
        gray_all = 1
    end

    -- update locations(both server and inc file)
    for _, s in ipairs(servers) do
        if s.GRAY then
            if s.locations then 
                local r, m = update_server_locations(s, gray_all)
                if not r then return nil, m end
            end
            if s.includes then
                for _, inc in ipairs(s.includes) do
                    if s[inc.INDEX].DELETE == nil and inc.locations then
                        -- if exist proxy_pass directive
                        local r, m = update_server_locations(inc, gray_all, 1)
                        if not r then return nil, m end
                        if r > 0 then
                            local file_name = inc.PARSE_CONF.file_name
                            local dir, fn = string.match(file_name, "(%S*/)(%S+)") 
                            if not fn then
                                fn = file_name
                                dir = ngx_conf_prefix
                            else
                                if string.sub(dir, 1, 1) ~= '/' then 
                                    dir = ngx_conf_prefix..dir
                                end
                            end
                            if is_test then dir = gray_test_fdir end
                            file_name = string.match(fn, "^gray_srv_inc_(%S+)$")
                            if gray_all then 
                                -- if file_name is not nil, do nothing
                                if file_name == nil then
                                    -- rename and recreate it
                                    file_name = dir.."gray_srv_inc_"..fn
                                    s[inc.INDEX].ARGS[2] = file_name
                                    local r, m = update_server_locations(inc, gray_all)
                                    if not r then return nil, m end
                                    -- set flag to recreate this include file
                                    if not files_to_be_create[file_name] then
                                        files_to_be_create[file_name] = 1
                                        s[inc.INDEX].RECREATE = 1
                                    end
                                end
                            else
                                if file_name then
                                    -- roll back to original name
                                    file_name = dir..file_name
                                    s[inc.INDEX].ARGS[2] = file_name
                                    -- if original file is deleted, restore it from current inc file
                                    if not is_file_exist(file_name) then
                                        local r, m = update_server_locations(inc, gray_all)
                                        if not r then return nil, m end
                                        if not files_to_be_create[file_name] then
                                            files_to_be_create[file_name] = 1
                                            s[inc.INDEX].RECREATE = 1
                                        end
                                    end
                                end
                            end
                        end
                    end
                end
            end
            -- insert include gray_sid_srv.conf line
            local directive = {} 
            local args = {}
            table.insert(args, "include")
            if is_test then
                table.insert(args, gray_test_fdir.."gray_"..jservers.sid.."_srv.conf")
            else
                table.insert(args, "gray_"..jservers.sid.."_srv.conf")
            end
            directive.ARGS = args
            ngx_conf_insert_head(s, directive)
        end
    end

    return OK
end


--- Update server blocks of nginx configure file
-- @param ngx_conf_http The content of nginx http confs
-- @param ngx_conf_servers The modified Servers Table
-- @param dowhat What to do
-- @return OK on success
-- @return nil and error message on error
local function update_ngx_conf(ngx_conf_http, ngx_conf_servers, jservers, dowhat)
    -- update http include file
    local is_test
    if dowhat == 'test' then is_test = 1 end

    local r, m = update_server_blocks(ngx_conf_servers, jservers, is_test)
    if r == nil then return nil, m end

    -- delete unused include lines
    for sid, value in pairs(server_sids) do
        if value == 0 then
            if ngx_conf_http.includes then
                for _, inc in ipairs(ngx_conf_http.includes) do
                    if inc.PARSE_CONF.file_name == "gray_"..sid.."_http.conf" then 
                        ngx_conf_http[inc.INDEX].DELETE = 1
                    end
                end
            end
        end
    end
    -- insert a new one
    local directive = {}
    if is_test then
        directive.ARGS = {"include", gray_test_fdir.."gray_"..jservers.sid.."_http.conf"}
    else
        directive.ARGS = {"include", "gray_"..jservers.sid.."_http.conf"}
    end
    ngx_conf_insert_head(ngx_conf_http, directive)

    return OK
end


local function backup_original_conf(sid)
    -- remove original files
    for fname in utility.dir(gray_backup_fdir) do
        if fname ~= "." and fname ~= ".." then
            os.remove(gray_backup_fdir..fname)
        end
    end

    --local cmd = "rm -f "..gray_backup_fdir.."* >/dev/null 2>&1"
    --os.execute(cmd)

    local r, m
    -- main configure file
    local mcf_dir, mcf = string.match(ngx_conf_name, "(%S*/)(%S+)") 
    r, m = copy(ngx_conf_name, gray_backup_fdir..mcf)        
    if r == nil then return nil, m end

    if sid then
        local srv_fn = "gray_"..sid.."_srv.conf"
        local http_fn = "gray_"..sid.."_http.conf"
        r, m = copy(ngx_conf_prefix..srv_fn, gray_backup_fdir..srv_fn)        
        if r == nil then return nil, m end
        r, m = copy(ngx_conf_prefix..http_fn, gray_backup_fdir..http_fn)        
        if r == nil then return nil, m end
    end

    for s, _ in pairs(server_sids) do
        local srv_fn = "gray_"..s.."_srv.conf"
        local http_fn = "gray_"..s.."_http.conf"
        r, m = copy(ngx_conf_prefix..srv_fn, gray_backup_fdir..srv_fn)        
        if r == nil then return nil, m end
        r, m = copy(ngx_conf_prefix..http_fn, gray_backup_fdir..http_fn)        
        if r == nil then return nil, m end
    end

    for f, _ in pairs(gray_sinc_fns) do
        local dir, fn = string.match(f, "(%S*/)(%S+)") 
        if dir == nil then
            fn = f
            dir = ngx_conf_prefix
        else
            if not string.match(f, "^/") then dir = ngx_conf_prefix..dir end
        end

        r, m = copy(dir..fn, gray_backup_fdir..fn)
        if r == nil then return nil, m end
    end

    for f, _ in pairs(gray_hinc_fns) do
        local dir, fn = string.match(f, "(%S*/)(%S+)") 
        if fn == nil then
            return nil, "http include file not with full path, it may be a bug"
        end
        r, m = copy(f, gray_backup_fdir..fn)
        if r == nil then return nil, m end
    end

    gray_backupped = 1
    return OK
end


local function remove_original_conf() 
    for k, v in pairs(server_sids) do
        if v == 0 then 
            os.remove(ngx_conf_prefix.."gray_"..k.."_http.conf")
            os.remove(ngx_conf_prefix.."gray_"..k.."_srv.conf")
        end
    end
    for k, v in pairs(gray_sinc_fns) do
        if v == 0 then 
            os.remove(k)
        end
    end
end


local function remove_success_created_files()
    for f, _ in pairs(success_created_files) do
        os.remove(f)
    end
end


local function restore_original_conf()
    gray_restoring = 1
    local bk_path = gray_backup_fdir

    -- ngx_conf_name always start with /
    local mcf_dir, mcf = string.match(ngx_conf_name, "(%S*/)(%S+)") 
    copy(bk_path..mcf, ngx_conf_name)

    -- gray_sinc_fns always begin with /
    for full_name, _ in pairs(gray_sinc_fns) do
        local dir, fn = string.match(full_name, "(%S*/)(%S+)") 
        if not fn then
            fn = full_name
            dir = ngx_conf_prefix
        end
        copy(bk_path..fn, dir..fn)
    end

    -- gray_hinc_fns always begin with /
    for full_name, _ in pairs(gray_hinc_fns) do
        local dir, fn = string.match(full_name, "(%S*/)(%S+)") 
        if not fn then
            fn = full_name
            dir = ngx_conf_prefix
        end
        copy(bk_path..fn, dir..fn)
    end

    -- gray configure
    for fname in utility.dir(bk_path) do
        if string.match(fname, "^gray_[^_]+_http%.conf$") then
            copy(bk_path..fname, ngx_conf_prefix..fname)
        end
        if string.match(fname, "^gray_[^_]+_srv%.conf$") then
            copy(bk_path..fname, ngx_conf_prefix..fname)
        end
    end
    --local cmd = "cp -f "..bk_path.."gray_*_http.conf "..bk_path.."gray_*_srv.conf "..ngx_conf_prefix
    --os.execute(cmd.." >/dev/null 2>&1")
    gray_restoring = nil
end


local function save_error_files(dowhat, status, sid)
    if status == nil then status = "" end
    local suffix = "."..os.time().."."..dowhat.."."..status.."."..sid

    if dowhat == "test" then
        local jfn = "gray_"..sid.."_conf.json"
        local f = io.open(gray_error_fdir..jfn..suffix, "w")
        f:write(ngx.var.request_body)
        f:close()
    end

    for ffn, _ in pairs(success_created_files) do
        local fn = string.match(ffn, "%S*/(%S+)") 
        copy(ffn, gray_error_fdir..fn..suffix)
    end
end


local function test_ngx_conf(ngx_bin_name, ngx_conf_name)
    local tmp_file = gray_test_fdir.."gray_boc.tmp"
    os.execute(ngx_bin_name.." -t -c "..ngx_conf_name..">"..tmp_file.." 2>&1")

    local f, m = io.open(tmp_file, "r")
    if not f then
        return nil, m
    end

    local rst = f:read("*all")
    f:close()
  
    os.remove(tmp_file)
    local num
    _, num = string.gsub(rst, "\n", "\n")

    -- TODO relax check
    
    if num == 2 and string.match(rst, "test is successful") and string.match(rst, "syntax is ok") then
        return OK
    else
        return nil, rst
    end
end


local function do_reconfigure(ngx_bin_name, ngx_conf_name)
    local gray_error
    local tmp_file = gray_test_fdir.."gray_boc.tmp"
    os.execute(ngx_bin_name.." -s reload -c "..ngx_conf_name.." >"..tmp_file.." 2>&1")

    local f, m = io.open(tmp_file, "r")
    if not f then
        return nil, "something wrong when do nginx configure test"
    end

    local rst = f:read("*all")
    f:close()

    os.remove(tmp_file)

    if #rst > 0 then 
        return nil, rst
    end
    return OK
end


local function test_and_reload(dowhat, ngx_conf_name, sid, status)
    local pass, em, r, m
    r, m = test_ngx_conf(ngx_bin_name, ngx_conf_name)
    if r ~= OK then
        save_error_files(dowhat, status, sid)
        remove_success_created_files()
        if dowhat ~= 'test' then restore_original_conf() end
        return nil, "configure test failed\n"..m, 1300
    end

    if dowhat == 'test' then 
        remove_success_created_files()
        return OK
    end

    r, m = do_reconfigure(ngx_bin_name, ngx_conf_name)

    if r ~= nil then
        -- check nginx status
        for i = 1, gray_try_times do
            sleep()
            local loc = gray_query_loc
            loc = loc.."?sid="..sid
            local res = ngx.location.capture(loc) 
            if res and res.status == ngx.HTTP_OK and res.body then
                if string.match(res.body, '\"'..status..'\"') then pass = 1 end
            end
            -- OK leave
            if pass == 1 then return OK end
        end
        m = "can not reload for strategy "..sid
    end

    -- reload error, roll back to original configure
    remove_success_created_files()
    restore_original_conf()
    r, em = do_reconfigure(ngx_bin_name, ngx_conf_name)
    if r ~= nil then
        for i = 1, gray_try_times do
            sleep()
            local loc = gray_query_loc
            local res = ngx.location.capture(loc) 
            if res and res.status == ngx.HTTP_OK then
                pass = 1
            end
            if pass == 1 then break end
        end
        em = "can not recover to original status!!"
    end
    if pass == nil then
       return nil, "reload nginx failed\n"..m.."\nNGINX SERVER IS DOWN!! PLEASE CONTACT PE RIGHT NOW!!\n"..em, 1000
    else 
       return nil, "reload nginx failed\n"..m, 1100
    end
end


local function create_gray_include_files(http_inc_file, srv_inc_file, json, gray_switch, dowhat)
    local r, m 
    r, m = create_http_gconf_file(http_inc_file, json)
    if r == nil then
        if dowhat ~= 'test' then restore_original_conf() end
        return nil, "create gray http include file failed\n"..m
    end
    success_created_files[http_inc_file] = 1

    r, m = create_srv_gconf_file(srv_inc_file, json, gray_switch)
    if r == nil then
        remove_success_created_files()
        if dowhat ~= 'test' then restore_original_conf() end
        return nil, "create gray server include file failed\n"..m
    end
    success_created_files[srv_inc_file] = 1
    return OK
end


local function check_sid_status(sid, dowhat, arg)
    local gray_switch = arg
    local cleared_in_mem = arg
    local http_inc_file = "gray_"..sid.."_http.conf" 
    local srv_inc_file  = "gray_"..sid.."_srv.conf" 

    local r, m = ngx_conf_parse(global_cf, ngx_conf_name)
    if r == nil then return nil, m end

    get_server_name_map(ngx_conf_servers)

    ngx_conf_checksum = calculate_conf_checksum()
    update_conf_checksum(ngx_conf_checksum, dowhat, sid)

    r = nil
    if ngx_conf_http.includes then
        for _, inc in ipairs(ngx_conf_http.includes) do
            if inc.PARSE_CONF.file_name == http_inc_file then
                if dowhat == 'clear' then
                    ngx_conf_http[inc.INDEX].DELETE = 1
                end
                r = 1
            end
        end
    end

    if r == nil then
        return 'NO'
    end

    local srv_inc_conf
    for _, s in ipairs(ngx_conf_servers) do
        if s.includes then
            for _, inc in ipairs(s.includes) do
                if inc.PARSE_CONF.file_name == srv_inc_file then
                    srv_inc_conf = inc
                    if dowhat == 'clear' then
                        s[inc.INDEX].DELETE = 1
                    end
                    s.GRAY = 0
                end
            end
        end
    end

    if srv_inc_conf == nil then
        return 'NO'
    end

    if dowhat == 'clear' then
        if cleared_in_mem == 1 then
            return nil, "configure in memory(0) not accordant with configure in file(1)"
        end
        for _, s in ipairs(ngx_conf_servers) do
            if s.GRAY == 0 then
                -- is server in include file?
                if s.PARENT.INCLUDE then
                    local directive = s.PARENT.PARENT[s.PARENT.INDEX]
                    local file_name = directive.ARGS[2]
                    local dir, fn = string.match(file_name, "(%S*/)(%S+)") 
                    if not fn then
                        fn = file_name
                        dir = ngx_conf_prefix
                    else
                        if string.sub(dir, 1, 1) ~= '/' then 
                            dir = ngx_conf_prefix..dir
                        end
                    end
                    directive.ARGS[2] = dir..fn
                    gray_hinc_fns[directive.ARGS[2]] = 1 
                    directive.RECREATE = 1
                end
                if s.includes then
                    for _, inc in ipairs(s.includes) do
                        -- gray_sinc_sid_xx.conf always begin with /
                        local directory, file_name = string.match(inc.PARSE_CONF.file_name, "(%S*/)gray_srv_inc_([^/]+)$")
                        if file_name and string.sub(directory, 1, 1) == '/' then
                            gray_sinc_fns[inc.PARSE_CONF.file_name] = 0
                            s[inc.INDEX].ARGS[2] = directory..file_name
                        end
                    end
                end
                if s.locations then
                    local r, m = update_server_locations(s, nil)
                    if not r then return nil, m end
                end
            end
        end
        for _, s in ipairs(ngx_conf_servers) do
            if s.GRAY ~= 0 and s.includes then
                for _, inc in ipairs(s.includes) do
                    -- gray_sinc_sid_xx.conf always begin with /
                    local directory, file_name = string.match(inc.PARSE_CONF.file_name, "(%S*/)gray_srv_inc_([^/]+)$")
                    if file_name and string.sub(directory, 1, 1) == '/' then
                        gray_sinc_fns[inc.PARSE_CONF.file_name] = 1
                    end
                end
            end
        end
        return OK
    end

    local status = 'on'
    local directive
    for _, d in ipairs(srv_inc_conf) do
        if d.ARGS[1] == 'gray_switch' then
            directive = d
            status = d.ARGS[2]
        end
    end

    if gray_switch and gray_switch ~= status then
        if directive then
            directive.ARGS[2] = gray_switch
        else
            directive = {}
            directive.ARGS = {'gray_switch', gray_switch}
            ngx_conf_insert_head(srv_inc_conf, directive)
        end
        r, m = ngx_conf_to_file(srv_inc_conf, ngx_conf_prefix..srv_inc_file, success_created_files)
        if r == nil then return nil, m end
    end

    return status
end


local function save_gray_json(json_str, sid)
    local fn = gray_json_fdir.."gray_"..sid.."_conf.json"
 
    local f, m = io.open(fn, "w")
    if f == nil then
        return nil, m
    end
    f:write(json_str)
    f:close()
    return OK
end


local function check_nginx_status()
    -- check if nginx is running
    local f, m = io.open(ngx_pid_name, "r")
    if f == nil then
        return nil, log_print("ERROR", "nginx is not running\n", 1100, m)
    end
    local pid = f:read("*all")
    f:close()
    if #pid == 0 then 
        return nil, log_print("ERROR", "nginx is not running", 1100)
    end

    -- check if somebody is updating nginx
    local ngx_pid_oldbin_fn = ngx_pid_name..".oldbin"
    if is_file_exist(ngx_pid_oldbin_fn) then
        return nil, log_print("ERROR", "someone is updating nginx", 1400)
    end

    -- check if somebody is editting the configure file
    local ngx_conf_swp_fn = string.gsub(ngx_conf_name, "/([^/]+)$", "/.%1.swp")
    if is_file_exist(ngx_conf_swp_fn) then
        return nil, log_print("ERROR", ngx_conf_swp_fn.." is found, someone may be editting the nginx configure file", 1400)
    end

-- check original configure (-t)
--local r, m = test_ngx_conf(ngx_bin_name, ngx_conf_name)
--if r == nil then
--    return log_print("ERROR", "something wrong with the current nginx configure file\n", 1150, m)
--end

    return OK
end


local function do_work()
local status = ngx.var['arg_status']
if dowhat == 'init' and status == 'json' then
    local json_str = ngx.var.request_body
    if json_str == nil or json_str == "" then return log_print("ERROR", "no request body", 1300) end

    local v, m = check_json_format(json_str)
    if v == nil then
        return log_print("ERROR", m, 1300)
    end
    
    local  r, m = save_gray_json(json_str, v.sid)
    if r == OK then 
        return log_print("SUCCESS")
    else
        return log_print("ERROR", "save json file failed\n", 1150, m)
    end
elseif (dowhat == 'init' and (status == 'start' or status == 'stop'))
    or dowhat == 'test' then
    local sid, jservers, f, r, m, c
    local source, in_file
    local gray_switch = "on"
    if dowhat == 'init' then
        sid = ngx.var.arg_sid
        if sid == nil or sid == "" then return log_print("ERROR", "no strategy id", 1300) end
        if string.match(sid, "_")  then return log_print("ERROR", "invalid sid format", 1300) end
        if check_nginx_status() == nil then return end
        if status == 'stop' then gray_switch = "off" end
        source = gray_json_fdir.."gray_"..sid.."_conf.json"
        in_file = 1
    else
        source = ngx.var.request_body
        if source == nil or source == "" then return log_print("ERROR", "no request body", 1300) end
    end
    -- parse json
    jservers, m = parse_json_from(source, in_file)
    if jservers == nil then
        return log_print("ERROR", "parse json failed\n", 1300, m)
    end

    if dowhat == 'test' then
        sid = jservers.sid
    end
    if sid == nil or sid == "" then return log_print("ERROR", "no strategy id", 1300) end

    -- check strategy status
    local loc = gray_query_loc
    loc = loc.."?sid="..sid
    local res = ngx.location.capture(loc) 
    if res == nil then 
        return log_print("ERROR", "server unknown error", 1400)
    end

    if res.status == ngx.HTTP_OK and res.body then
        if not string.match(res.body, '\"NO\"') then
            if dowhat == 'test' then
              local status = string.match(res.body, '\"(start)\"') or string.match(res.body, '\"(stop)\"') 
              if status then
              return log_print("ERROR","strategy "..sid.." is already running,status:"..status..",clear before create a new one",1300)
              else  
              return log_print("ERROR", "something wrong when query status", 1400)
              end
            else
                local rsp_status = string.match(res.body, '\"(start)\"') or string.match(res.body, '\"(stop)\"') 
                if rsp_status == nil then
                    return log_print("ERROR", "something wrong when query status", 1400)
                elseif rsp_status == status then
                    return log_print("SUCCESS")
                else
                    return log_print("ERROR", "strategy "..sid.." is already running, its status is "..rsp_status, 1300)
                end
            end
        end
    else
        return log_print("ERROR", "please try again later", 1400)
    end

    --nginx_server_name = string.match(jservers.conf.hosts, "%S+")
    --ngx.var.nginx_query_host = nginx_server_name

    -- check if it's the right sid
    if sid ~= jservers.conf.sid then
        return log_print("ERROR", "json file is broken, please upload again", 1300)
    end

    -- parse nginx configure file
    r, m = ngx_conf_parse(global_cf, ngx_conf_name)
    if r == nil then
        return log_print("ERROR", "parse nginx configure file failed\n", 1150, m)
    end

    get_server_name_map(ngx_conf_servers)

    if dowhat == "init" then
        ngx_conf_checksum = calculate_conf_checksum()
        update_conf_checksum(ngx_conf_checksum, "init", jservers)
    end

    -- update nginx configure using json configure 
    r, m = update_ngx_conf(ngx_conf_http, ngx_conf_servers, jservers, dowhat)
    if r == nil then
        return log_print("ERROR", "update nginx configure failed\n", 1300, m)
    end

    local conf_prefix = gray_test_fdir
    local new_conf_name = ngx_conf_name..".test."..sid
    if dowhat == 'init' then
        -- backup original nginx configure files
        r, m = backup_original_conf()
        if r == nil then
            return log_print("ERROR", "backup original nginx configure files failed\n", 1150, m)
        end
        -- remove unused files
        remove_original_conf()
        conf_prefix = ngx_conf_prefix
        new_conf_name = ngx_conf_name
    end

    -- create new files
    local http_inc_file = conf_prefix.."gray_"..sid.."_http.conf"
    local srv_inc_file  = conf_prefix.."gray_"..sid.."_srv.conf"
    r, m = create_gray_include_files(http_inc_file, srv_inc_file, jservers.conf, gray_switch, dowhat)
    if r == nil then
        return log_print("ERROR", m, 1150)
    end

    -- create new main configure file
    r, m = ngx_conf_to_file(global_cf.ngx_conf, new_conf_name, success_created_files)
    if r == nil then
        remove_success_created_files()
        if dowhat == 'init' then restore_original_conf() end
        return log_print("ERROR", "create new configure files failed\n", 1150, m)
    end

    if dowhat == "init" then
        r, m = verify_conf_checksum(new_conf_name, ngx_conf_checksum)
        if r == nil then
            save_error_files(dowhat, status, sid)
            remove_success_created_files()
            restore_original_conf()
            return log_print("ERROR", "something wrong with the new configure file\n", 1150, m)
        end
    end

    -- test and reload new configure file
    r, m, c = test_and_reload(dowhat, new_conf_name, sid, status)
    if r == nil then
        return log_print("ERROR", m, c)
    end

    return log_print("SUCCESS")
elseif dowhat == 'stop' or dowhat == 'start' then
    local f, r, m, c
    local sid = ngx.var.arg_sid
    if sid == nil or sid == "" then return log_print("ERROR", "no strategy id", 1300) end
    if string.match(sid, "_")  then return log_print("ERROR", "invalid sid format", 1300) end
    if check_nginx_status() == nil then return end
    local gray_switch = "on"
    if dowhat == 'stop' then gray_switch = "off" end

    -- query gray status
    local loc = gray_query_loc
    loc = loc.."?sid="..sid
    local res = ngx.location.capture(loc) 
    if res == nil then 
        return log_print("ERROR", "server unknown error", 1400)
    end

    if res.status == ngx.HTTP_OK and res.body then
        if string.match(res.body, '\"start\"') and dowhat == 'start' then return log_print('SUCCESS') end
        if string.match(res.body, '\"stop\"')  and dowhat == 'stop'  then return log_print('SUCCESS') end
        if string.match(res.body, '\"NO\"') then return log_print('NO') end
        local hosts = string.match(res.body, "\"hosts\":\"([^\"]+)\"")
        if not hosts then
            return log_print("ERROR", "server busy(200), please try again later", 1400)
        end
    else
        return log_print("ERROR", "server busy, please try again later", 1400)
    end

    server_sids[sid] = 1
    -- backup original nginx configure files
    r, m = backup_original_conf()
    if r == nil then
        return log_print("ERROR", "backup original nginx configure files failed\n", 1150, m)
    end

    -- modify gray_switch in gray_sid_srv.conf
    r, m = check_sid_status(sid, dowhat, gray_switch)
    if r == 'NO' then return log_print('ERROR', "can not find configure file for strategy ", 1150, sid) end
    if r == nil then
        remove_success_created_files()
        restore_original_conf()
        return log_print('ERROR', m, 1100) 
    end

    r, m = verify_conf_checksum(ngx_conf_name, ngx_conf_checksum)
    if r == nil then
        save_error_files(dowhat, dowhat, sid)
        remove_success_created_files()
        restore_original_conf()
        return log_print("ERROR", "something wrong with the new configure file\n", 1150, m)
    end

    -- test and reload new configure file
    r, m, c = test_and_reload(dowhat, ngx_conf_name, sid, dowhat)
    if r == nil then
        return log_print("ERROR", m, c)
    end
    return log_print("SUCCESS")
elseif dowhat == 'clear' then
    local r, f, m, c
    local cleared_in_mem
    local sid = ngx.var.arg_sid
    if sid == nil or sid == "" then return log_print("ERROR", "no strategy id", 1300) end
    if string.match(sid, "_")  then return log_print("ERROR", "invalid sid format", 1300) end
    if check_nginx_status() == nil then return end
    local loc = gray_query_loc
    loc = loc.."?sid="..sid

    local res = ngx.location.capture(loc) 
    if res == nil then 
        return log_print("ERROR", "server unknown error", 1400)
    end

    if res.status == ngx.HTTP_OK and res.body then
        if string.match(res.body, "\"NO\"") then
            cleared_in_mem = 1
        end
    else
        return log_print("ERROR", loc.." get ", 1400, res.status)
    end

    -- clear sid in main configure file
    r, m = check_sid_status(sid, dowhat, cleared_in_mem)
    if r == nil then 
        return log_print('ERROR', m, 1100)
    end
    if r == 'NO' then
        if cleared_in_mem then
            os.remove(gray_json_fdir.."gray_"..sid.."_conf.json")
            return log_print("SUCCESS")
        else
            return log_print("ERROR", "configure in memory(1) not accordant with configure in file(0)", 1150)
        end
    end

    server_sids[sid] = 0
    -- backup original configure files
    r, m = backup_original_conf()
    if r == nil then
        return log_print("ERROR", "backup original nginx configure files failed\n", 1150, m)
    end

    -- remove unused files
    remove_original_conf()

    r, m = ngx_conf_to_file(global_cf.ngx_conf, ngx_conf_name, success_created_files)
    if r == nil then 
        remove_success_created_files()
        restore_original_conf()
        return log_print("ERROR", "create new configure files failed\n", 1150, m)
    end

    r, m = verify_conf_checksum(ngx_conf_name, ngx_conf_checksum)
    if r == nil then
        save_error_files(dowhat, 'NO', sid)
        remove_success_created_files()
        restore_original_conf()
        return log_print("ERROR", "something wrong with the new configure file\n", 1150, m)
    end

    -- test and reload new configure file
    r, m, c = test_and_reload(dowhat, ngx_conf_name, sid, 'NO')
    if r == nil then
        return log_print("ERROR", m, c)
    end

    -- remove json file
    os.remove(gray_json_fdir.."gray_"..sid.."_conf.json")
    return log_print("SUCCESS")
elseif dowhat == 'look' then
    local loc = gray_query_loc
    local sid = ngx.var.arg_sid
    if sid ~= nil and #sid > 0 then loc = loc.."?sid="..sid end

    local res = ngx.location.capture(loc) 
    if res == nil then 
        return log_print("ERROR", "server unknown error", 1400)
    end
    if res.status == ngx.HTTP_OK and res.body then
        local body = res.body
        if string.match(res.body, "\"NO\"") then
            if sid and #sid > 0 then 
                if is_file_exist(gray_json_fdir.."gray_"..sid.."_conf.json") then
                    body = "{\"status\":\"JSON\"}" 
                end
            end
        end
        return log_print("RAW", body)
    else
        return log_print("ERROR", loc.." return code:", 1400, res.status)
    end
else
    return log_print("ERROR", "unknown operation", 1300)
end
end


local function lock()
    -- lua_shared_dict lua_lock 128k;
    local lua_lock = ngx.shared.lua_lock
    -- expired time is 30s
    local succ, err, forcible = lua_lock:add("lua_lock", 1, 20)
    if succ == true then
        return true
    else
        return false
    end
end
    

local function unlock()
    local lua_lock = ngx.shared.lua_lock
    lua_lock:delete("lua_lock")
end


local locked

if dowhat ~= 'look' then
    locked = lock()
end

if locked == false then
    return log_print("ERROR", "someone is operating, please try again later,\
if you have tried for a long time, there may be something wrong with the server,\
please contact PE to solve the problem", 1400)
else
    local r, v = pcall(do_work)
    if not r then 
        if gray_backupped and gray_restoring == nil then
            remove_success_created_files()
            restore_original_conf()
        end
        if dowhat ~= 'look' then
            unlock()
        end
        return log_print("ERROR", "fatal error!! there may be some bugs in server, \
PLEASE CONTACT PE TO CHECK IF THE NGINX SERVER WAS CRASHED!!!\ndue to:", 1200, v)  
    end
    if dowhat ~= 'look' then
        unlock()
    end
end

